using System.Collections.Generic;
using Roslyn.Compilers.CSharp;
using System;

namespace ContentOverContainer.Common.Converter
{
    public static class CastConverter
    {
        public static ChangeNodeInfo ConvertCastExpression(CastExpressionSyntax pExprSyntax, ConverterContext pConverterData)
        {
            SimpleNameSyntax memberToCast = pExprSyntax.Expression.DescendantNodesAndSelf(x => x is SimpleNameSyntax) as SimpleNameSyntax;
            
            var parentCastExpr = pExprSyntax.Parent as CastExpressionSyntax;
            if (parentCastExpr != null)
            {
                pExprSyntax = parentCastExpr;
            }
            var typeInfo = pExprSyntax.GetTypeInfo(pConverterData.SemanticModel);
            if (AnyAttribute.EqualsName(typeInfo.Name) )//R-MCast2 or R-MCast3 (upcast)
            {
                var multiTypeCast = (pExprSyntax.Expression as CastExpressionSyntax);
                if (multiTypeCast != null)
                    memberToCast = multiTypeCast.Expression as SimpleNameSyntax;
                MultiplicityKind memberMultiKind;
                TypeSyntax memberType;
                var memberDecl = GetTypeAndKind(pConverterData, memberToCast, out memberMultiKind, out memberType);
                if (memberMultiKind == MultiplicityKind.Any) //R-MCast2
                {
                    var multiType = memberDecl.GetMultiplicityType();
                    var genMemberType = multiTypeCast.Type as GenericNameSyntax;
                    string collectionType = genMemberType.Identifier.ToString();
                    var result = Syntax.ParseExpression(String.Format("new {0}<{1}>() {{{2}}};", collectionType, multiType, memberToCast));
                    return new ChangeNodeInfo(result) {OrgSyntaxNode= pExprSyntax};
                }
                else//R-MCast3
                {
                    var genMemberType = multiTypeCast.Type as GenericNameSyntax;
                    string collectionType = genMemberType.Identifier.ToString();

                    var result = UpcastToAny(pExprSyntax, memberType, memberToCast, collectionType);
                    return result;
                }
            }
            else
            {
                memberToCast = pExprSyntax.Expression as SimpleNameSyntax;
                MemberDeclarationSyntax memberDecl = memberToCast.GetMemberDeclaration(pConverterData.SemanticModel);                
                if (memberDecl == null)//local variable
                {
                    return new ChangeNodeInfo(pExprSyntax);//local variables can't be annotated
                }
                if (OptionAttribute.EqualsName(typeInfo.Name)) //R-MCast1 (downcast)
                {                    
                    var parameter = new List<ParameterPair>();
                    parameter.Add(new ParameterPair() { Type = memberDecl.GetMultiplicityType(), Name = memberToCast });

                    var methodBody = (BlockSyntax)Syntax.ParseStatement(
                        String.Format("{{//" + pExprSyntax.ToString() + Environment.NewLine +
                                      "if({0}.Count==0) return null;" + Environment.NewLine +
                                      "if({0}.Count==1) return {0}[0];" + Environment.NewLine +
                                      "else throw System.InvalidCastException(\"Wrong multipicity cast.\");" + Environment.NewLine+
                                       "}}" + Environment.NewLine
                                      , memberToCast));

                    var composition = MethodComposer.BuildMethod("Cast1", methodBody, parameter, memberDecl.GetDeclarationType().ToString());

                    var cast1Result = new ChangeNodeInfo(composition.InvocationCall) { OrgSyntaxNode = pExprSyntax };
                    var methodSubstitution = new ChangeNodeMethodSubstitution()
                    {
                        Class = pExprSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>(),
                        MethodDeclaration = composition.MethodDecl
                    };
                    cast1Result.MethodSubstitutionList.Add(methodSubstitution);
                    return cast1Result;
                }
                else //R-TCast (typecast)
                {
                    string collectionType = pExprSyntax.Type.ToString();
                    var genMemberType = pExprSyntax.Type as GenericNameSyntax;
                    if (genMemberType != null)
                        collectionType = genMemberType.Identifier.ToString();
                 
                    var parameter = new List<ParameterPair>();
                    parameter.Add(new ParameterPair() { Type = Syntax.ParseTypeName(collectionType), Name = memberToCast });

                    string tempResultName = "resList" + MethodComposer.RandomVarialbleName();
                    var methodBody = (BlockSyntax)Syntax.ParseStatement(
                        String.Format("{{//" + pExprSyntax.ToString() + Environment.NewLine +
                                      "{0}<{1}> {2} = new {0}<{1}>();" + Environment.NewLine +
                                      "foreach(var temp in {1}) {2}.Add(({1})temp);" + Environment.NewLine +
                                      "return {2};" +
                                      "}}" + Environment.NewLine
                                      , collectionType, typeInfo.Name, tempResultName, memberToCast));

                    var composition = MethodComposer.BuildMethod("CastT", methodBody, parameter, String.Format("{0}<{1}>", collectionType, typeInfo.Name));

                    var result = new ChangeNodeInfo(composition.InvocationCall) { OrgSyntaxNode = pExprSyntax };
                    var methodSubstitution = new ChangeNodeMethodSubstitution()
                    {
                        Class = pExprSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>(),
                        MethodDeclaration = composition.MethodDecl
                    };
                    result.MethodSubstitutionList.Add(methodSubstitution);
                    return result;
                }
            }
        }

        /// <summary>
        /// Upcasts to any. R-MCast3
        /// </summary>
        /// <param name="pExprSyntax">The expression syntax.</param>
        /// <param name="pMemberType">Type of the member.</param>
        /// <param name="pMemberToCast">The member to cast.</param>
        /// <param name="pCollectionType">Type of the collection.</param>
        /// <returns></returns>
        /// <author>
        /// Harald Binkle
        /// </author>
        public static ChangeNodeInfo UpcastToAny(SyntaxNode pExprSyntax, TypeSyntax pMemberType,
                                                     ExpressionSyntax pMemberToCast, string pCollectionType)
        {
            var parameter = new List<ParameterPair>();
            parameter.Add(new ParameterPair() {Type = pMemberType, Name = pMemberToCast});

            string tempResultName = "resultList";// + MethodComposer.RandomVarialbleName();
            var methodBody = (BlockSyntax) Syntax.ParseStatement(
                String.Format("{{//" + pExprSyntax.ToString() + Environment.NewLine +
                              "{0}<{1}> {2} = new {0}<{1}>();" + Environment.NewLine +
                              "if({3} != null) {2}.Add({3});" + Environment.NewLine +
                              "return {2};" +
                              "}}" + Environment.NewLine
                              , pCollectionType, pMemberType, tempResultName, pMemberToCast));

            var composition = MethodComposer.BuildMethod("Cast3", methodBody, parameter,
                                                         String.Format("{0}<{1}>", pCollectionType, pMemberType));

            var result = new ChangeNodeInfo(composition.InvocationCall) { OrgSyntaxNode = pExprSyntax };
            var methodSubstitution = new ChangeNodeMethodSubstitution()
            {
                Class = pExprSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>(),
                MethodDeclaration = composition.MethodDecl
            };
            result.MethodSubstitutionList.Add(methodSubstitution);
            return result;
        }

        /// <summary>
        /// Helper method for getting the kind and the type of the member to cast.
        /// </summary>
        /// <param name="pConverterData">The p converter data.</param>
        /// <param name="memberToCast">The member to cast.</param>
        /// <param name="memberMultiKind">Kind of the member multi.</param>
        /// <param name="memberType">Type of the member.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        private static MemberDeclarationSyntax GetTypeAndKind(ConverterContext pConverterData, SimpleNameSyntax pMemberToCast,
                                                              out MultiplicityKind pMemberMultiKind, out TypeSyntax pMemberType)
        {
            MemberDeclarationSyntax memberDecl = pMemberToCast.GetMemberDeclaration(pConverterData.SemanticModel);
            pMemberMultiKind = MultiplicityKind.None;
            pMemberType = null;
            if (memberDecl == null) //local variable
            {
                VariableDeclarationSyntax varDecl = pMemberToCast.GetVariableDeclaration(pConverterData.SemanticModel);
                if (varDecl != null)
                {
                    pMemberMultiKind = MultiplicityKind.None; //local variables can't be annotated
                    pMemberType = varDecl.Type;
                }
            }
            else
            {
                pMemberMultiKind = memberDecl.GetMultiplicityKind();
                if (pMemberMultiKind == MultiplicityKind.Any)
                    pMemberType = memberDecl.GetMultiplicityType();
                else
                    pMemberType = memberDecl.GetDeclarationType();
            }
            return memberDecl;
        }
    }
}